using MQTTnet.Core.Client;
using Microsoft.Extensions.Configuration;
using MQTTnet;
using System;
using RabbitMQ.Client;
using MQTTnet.Core;
using System.Text;
using Newtonsoft.Json;
using System.Collections.Generic;
using MQTTnet.Core.Packets;
using MQTTnet.Core.Protocol;
using System.Threading.Tasks;
using IotS.Core.Helpers;

namespace IotS.MessageService
{
    public class MessageHandler : IService
    {
        private readonly MSSettings _settings;

        private readonly MqttClient _mqttClient;

        private readonly QueueSub _sub;
        private readonly QueuePub _pub;

        public MessageHandler()
        {
            _settings = Program.Configuration.Get<MSSettings>();
            var mqttOptions = new MqttClientOptions
            {
                Server = _settings.Mqtt.Host,
                //UserName = _settings.Mqtt.UserName,
                //Password = _settings.Mqtt.Password
            };

            var factory = new ConnectionFactory
            {
                HostName = _settings.RabbitMQ.Host,
                UserName = _settings.RabbitMQ.UserName,
                Password = _settings.RabbitMQ.Password
            };
            factory.AutomaticRecoveryEnabled = true;

            _mqttClient = new MqttClientFactory().CreateMqttClient(mqttOptions);

            _sub = new QueueSub(true);
            _pub = new QueuePub();
        }

        public void Start()
        {
            try
            {
                Console.Write("Start Handler ");
                _sub.Start(ReceiveQ);
                Console.Write(".");
                InitMqtt();
                Console.Write(".");
                _mqttClient.ConnectAsync().Wait();
                Console.WriteLine(". OK!");
            }
            catch (Exception e)
            {
                Console.Write("Failed!");
                Console.WriteLine(e);
            }
        }

        private void ReceiveQ(string msg)
        {
            var cmd = JsonConvert.DeserializeObject<Command>(msg);
            var cmdParts = new List<string>();
            cmdParts.Add(cmd.Id);
            cmdParts.Add(cmd.Cmd);
            if (string.IsNullOrWhiteSpace(cmd.Parameter))
                cmdParts.Add(cmd.Parameter);

            if (string.IsNullOrWhiteSpace(cmd.Device))
            {
                return;
            }

            var command = string.Join("#", cmdParts).TrimEnd('#');

            var topic = _settings.Mqtt.InTopic + cmd.Device;
            var payload = Encoding.UTF8.GetBytes(command);

            _mqttClient.PublishAsync(new MqttApplicationMessage(topic, payload, MqttQualityOfServiceLevel.AtMostOnce, true));

            Console.WriteLine($"Send {command} -> {topic}");
        }

        private void InitMqtt()
        {
            _mqttClient.ApplicationMessageReceived += ApplicationMessageReceived;
            _mqttClient.Connected += MqttConnected;
            _mqttClient.Disconnected += MqttDisconnected;
        }

        #region Mqtt Events

        private void ApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e)
        {
            var payload = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
            var topic = e.ApplicationMessage.Topic;
            var message = new Message
            {
                Topic = topic,
                Payload = payload
            };

            Console.WriteLine($"{topic}:{payload} at {DateTime.Now:G}");

            _pub.SendTo(message);
        }

        private async void MqttConnected(object sender, EventArgs e)
        {
            //Console.WriteLine("### CONNECTED WITH SERVER ###");

            await _mqttClient.SubscribeAsync(new List<TopicFilter>{
                new TopicFilter(_settings.Mqtt.Topic,MqttQualityOfServiceLevel.ExactlyOnce),
                new TopicFilter(_settings.Mqtt.HeartTopic,MqttQualityOfServiceLevel.AtLeastOnce)
            });
        }

        private async void MqttDisconnected(object sender, EventArgs e)
        {
            Console.WriteLine("### DISCONNECTED FROM SERVER ###");
            await Task.Delay(TimeSpan.FromSeconds(5));

            try
            {
                await _mqttClient.ConnectAsync();
            }
            catch
            {
                Console.WriteLine("### RECONNECTING FAILED ###");
            }
        }

        #endregion

        #region IDisposable Support
        private bool disposedValue = false; // To detect redundant calls

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: dispose managed state (managed objects).
                    if (_pub != null)
                        _pub.Dispose();
                    if (_sub != null)
                        _sub.Dispose();
                    if (_mqttClient != null && _mqttClient.IsConnected)
                        _mqttClient.DisconnectAsync().Wait();

                }

                // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
                // TODO: set large fields to null.

                disposedValue = true;
            }
        }

        // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
        // ~MessageHandler() {
        //   // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
        //   Dispose(false);
        // }

        // This code added to correctly implement the disposable pattern.
        public void Dispose()
        {
            // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
            Dispose(true);
            // TODO: uncomment the following line if the finalizer is overridden above.
            // GC.SuppressFinalize(this);
        }
        #endregion
    }
}